
// ffmpegdemo.cpp : 定义控制台应用程序的入口点。
//
 
#include <stdio.h>
#include "encode.h" 


void CodecCtxCopy(AVCodecContext *dst, AVCodecContext *src)
{
	dst->codec_id   = src->codec_id;
	dst->codec_type = src->codec_type;
	dst->pix_fmt    = src->pix_fmt;
	dst->width      = src->width;
	dst->height     = src->height;
	//dst->b_frame_strategy = src->b_frame_strategy;

	dst->bit_rate = src->bit_rate;				//采样码率越大，目标文件越大
	//pCodecCtx->bit_rate_tolerance = 8000000; // 码率误差，允许的误差越大，视频越小
	//两个I帧之间的间隔
	dst->gop_size = src->gop_size;
 
	//编码帧率，每秒多少帧。下面表示1秒25帧
	dst->time_base.num = src->time_base.num;
	dst->time_base.den = src->time_base.den;
 
	//最小的量化因子
	dst->qmin = src->qmin;
	//最大的量化因子
	dst->qmax = src->qmax;
 
	//最大B帧数
	dst->max_b_frames = src->max_b_frames;
}


int encode_init(encode_t *enc, const char *out_file, AVFormatContext *iFormatCtx, AVCodecContext *iCodecCtx)
{
	int ret;
    AVFormatContext *pFormatCtx = NULL;
    AVCodecContext	*pCodecCtx  = NULL;
    AVCodec			*pCodec     = NULL;    
	
	// Set Option   
	AVDictionary    *param = NULL;

	/**
	 * 使用 avformat_alloc_context 和 av_guess_format 等组成
	 */
	avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
    if (!pFormatCtx) {
        printf("Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        return -1;
    }

 #if 0
	pCodecCtx = avcodec_alloc_context3(pCodec);//申请AVCodecContext，并初始化。  
	if (!pCodecCtx) 
	{
		//failed get AVCodecContext  
		return -1;
	}
#endif

	for (int i = 0; i < iFormatCtx->nb_streams; i++) 
	{        
		AVStream        *outStream;
		AVStream *inStream = iFormatCtx->streams[i];
		AVCodecParameters *in_codecpar = inStream->codecpar;
 
        // 2.2 将一个新流(out_stream)添加到输出文件(ofmt_ctx)
        outStream = avformat_new_stream(pFormatCtx, NULL);
        if (!outStream) {
            printf("Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            return -1;
        }
 
        // 2.3 将当前输入流中的参数拷贝到输出流中
        ret = avcodec_parameters_copy(outStream->codecpar, in_codecpar);
        if (ret < 0) {
            printf("Failed to copy codec parameters\n");
            return -1;
        }
		outStream->codecpar->codec_tag = 0;
		outStream->time_base.num = inStream->time_base.num;
		outStream->time_base.den = inStream->time_base.den;
	}

	//编解码上下文
	pCodecCtx = pFormatCtx->streams[0]->codec;

	CodecCtxCopy(pCodecCtx, iCodecCtx);

	// 编码器查找
	pCodec = avcodec_find_encoder(pCodecCtx->codec_id); 
	if (!pCodec) 
	{
		printf("cannot find encoder");
		return -1;
	}

	/* 打开编码器 */
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) 
	{
		printf("Failed to open encoder! \n");
		return -1;
	}
#if 0
	pCodecCtx = avcodec_alloc_context3(pCodec);//申请AVCodecContext，并初始化。  
	if (!pCodecCtx) 
	{
		//failed get AVCodecContext  
		return -1;
	}
#endif
	//手工调试函数，输出tbn、tbc、tbr、PAR、DAR的含义
	// 把获取到得参数全部输出
	av_dump_format(pFormatCtx, 0, out_file, 1);	//最后一个参数，如果是输出文件时，该值为1；如果是输入文件时，该值为0
# if 0
	 //打开FFmpeg的输入输出文件,成功之后创建的AVIOContext结构体。
	if (avio_open2(&pFormatCtx->pb, out_file, AVIO_FLAG_READ_WRITE,NULL,NULL) < 0) 
	{
		//Failed to open output file
		return -1;
	}
#endif	

	/* 写文件头（对于某些没有文件头的封装格式，不需要此函数。比如说MPEG2TS） */
	avformat_write_header(pFormatCtx, NULL);
	
    enc->pFormatCtx = pFormatCtx;
    enc->pCodecCtx  = pCodecCtx;
    enc->pCodec     = pCodec;

    return 0;
}



void encode_exit(encode_t *enc)
{
	avio_close(enc->pFormatCtx->pb);
	avformat_free_context(enc->pFormatCtx);
    // avformat_close_input(&enc->pFormatCtx);
    avcodec_close(enc->pCodecCtx);
}


int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index) 
{
	int ret;
	int got_frame;
	AVPacket enc_pkt;
	if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &AV_CODEC_CAP_DELAY))
	{
		return 0;
	}
	while (1) 
	{
		enc_pkt.data = NULL;
		enc_pkt.size = 0;
		av_init_packet(&enc_pkt);
		ret = avcodec_encode_video2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,NULL, &got_frame);
		av_frame_free(NULL);
		if (ret < 0)
		{
			break;
		}
		if (!got_frame)
		{
			ret = 0;
			break;
		}
		printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", enc_pkt.size);
		/* mux encoded frame */
		ret = av_write_frame(fmt_ctx, &enc_pkt);
		if (ret < 0)
		{
			break;
		}
	}
 
	return ret;
}


#if 0
int encode(encode_t *enc)
{
    int ret
    for (i = 0; i < nb_frames; i++) {
    {
        AVFrame *in_picture;
        av_init_packet(&pkt);

        ret = avcodec_send_frame(enc, in_picture);
        if (ret < 0)
            goto error;
    }
    

        while (1) {
            ret = avcodec_receive_packet(enc, &pkt);
            update_benchmark("encode_video %d.%d", ost->file_index, ost->index);
            if (ret == AVERROR(EAGAIN))
                break;
            if (ret < 0)
                goto error;

            if (debug_ts) {
                av_log(NULL, AV_LOG_INFO, "encoder -> type:video "
                       "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s\n",
                       av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &enc->time_base),
                       av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &enc->time_base));
            }

            if (pkt.pts == AV_NOPTS_VALUE && !(enc->codec->capabilities & AV_CODEC_CAP_DELAY))
                pkt.pts = ost->sync_opts;

            av_packet_rescale_ts(&pkt, enc->time_base, ost->mux_timebase);

            if (debug_ts) {
                av_log(NULL, AV_LOG_INFO, "encoder -> type:video "
                    "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s\n",
                    av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &ost->mux_timebase),
                    av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &ost->mux_timebase));
            }

            frame_size = pkt.size;
            output_packet(of, &pkt, ost, 0);

            /* if two pass, output log */
            if (ost->logfile && enc->stats_out) {
                fprintf(ost->logfile, "%s", enc->stats_out);
            }
        }
    }
}

int encode2()
{
	avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_picture);
}


int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index) 
{
	int ret;
	int got_frame;
	AVPacket enc_pkt;
	if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &CODEC_CAP_DELAY))
	{
		return 0;
	}
	while (true) 
	{
		enc_pkt.data = NULL;
		enc_pkt.size = 0;
		av_init_packet(&enc_pkt);
		ret = avcodec_encode_video2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,NULL, &got_frame);
		av_frame_free(NULL);
		if (ret < 0)
		{
			break;
		}
		if (!got_frame)
		{
			ret = 0;
			break;
		}
		printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", enc_pkt.size);
		/* mux encoded frame */
		ret = av_write_frame(fmt_ctx, &enc_pkt);
		if (ret < 0)
		{
			break;
		}
	}
 
	return ret;
}
 
int main()
{
	AVFormatContext* pFormatCtx;
	AVOutputFormat* pOutputFmt;
	AVStream* video_st;
	AVCodecContext* pCodecCtx;
	AVCodec* pCodec;
	AVPacket pkt;
	uint8_t* picture_buf;
	AVFrame* pFrame;
	int picture_size;
	int y_size;
	int framecnt = 0;
	int in_w = 640, in_h = 272;					//视频图像宽高
	const char* out_file = "output.h264";
 
	int i, j;
	int num;
	int index_y, index_u, index_v;
	uint8_t *y_, *u_, *v_, *in;
 
	int got_picture = 0;
	int ret;
 
	av_register_all();
	//alloc avformat context
	pFormatCtx = avformat_alloc_context();
	//猜测类型,返回输出类型。
	pOutputFmt = av_guess_format(NULL, out_file, NULL);
	pFormatCtx->oformat = pOutputFmt;
	//打开FFmpeg的输入输出文件,成功之后创建的AVIOContext结构体。
	if (avio_open2(&pFormatCtx->pb, out_file, AVIO_FLAG_READ_WRITE,NULL,NULL) < 0) 
	{
		//Failed to open output file
		return -1;
	}
 
	//除了以下方法，另外还可以使用avcodec_find_encoder_by_name()来获取AVCodec  
	pCodec = avcodec_find_encoder(pOutputFmt->video_codec);//获取编码器  
	if (!pCodec) 
	{
		//cannot find encoder  
		return -1;
	}
 
	pCodecCtx = avcodec_alloc_context3(pCodec);//申请AVCodecContext，并初始化。  
	if (!pCodecCtx) 
	{
		//failed get AVCodecContext  
		return -1;
	}
 
	FILE * in_file = fopen("640-272.yuv", "rb");   //打开原始yuv数据文件
 
	/* 创建输出码流的AVStream */
	video_st = avformat_new_stream(pFormatCtx, 0);
	video_st->time_base.num = 1;
	video_st->time_base.den = 25;
	if (video_st == NULL) 
	{
		return -1;
	}
	//Param that must set  
	pCodecCtx = video_st->codec;
	//pCodecCtx->codec_id =AV_CODEC_ID_HEVC;  //H265
	pCodecCtx->codec_id = AV_CODEC_ID_H264;
	pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
	pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
	pCodecCtx->width = in_w;
	pCodecCtx->height = in_h;
	pCodecCtx->b_frame_strategy = true;
	/*
	码率
	bit_rate/-bt tolerance 设置视频码率容忍度kbit/s （固定误差）
	rc_max_rate/-maxrate bitrate设置最大视频码率容忍度 （可变误差）
	rc_min_rate/-minrate bitreate 设置最小视频码率容忍度（可变误差）
	rc_buffer_size/-bufsize size 设置码率控制缓冲区大小
	如何设置固定码率编码 ?
	bit_rate是平均码率，不一定能控制住
	c->bit_rate = 400000;
	c->rc_max_rate = 400000;
	c->rc_min_rate = 400000;
	提示  [libx264 @ 00c70be0] VBV maxrate specified, but no bufsize, ignored
	再设置  c->rc_buffer_size = 200000;  即可。如此控制后编码质量明显差了。
	*/
	pCodecCtx->bit_rate = 400000;				//采样码率越大，目标文件越大
	//pCodecCtx->bit_rate_tolerance = 8000000; // 码率误差，允许的误差越大，视频越小
	//两个I帧之间的间隔
	pCodecCtx->gop_size = 15;
 
	//编码帧率，每秒多少帧。下面表示1秒25帧
	pCodecCtx->time_base.num = 1;
	pCodecCtx->time_base.den = 25;
 
	//最小的量化因子
	pCodecCtx->qmin = 10;
	//最大的量化因子
	pCodecCtx->qmax = 30;
 
	//最大B帧数
	pCodecCtx->max_b_frames = 3;
 
	// Set Option  
	AVDictionary *param = 0;
	//H.264  
	if (pCodecCtx->codec_id == AV_CODEC_ID_H264) 
	{
		/*
		preset的参数主要调节编码速度和质量的平衡，有ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo这10个选项，从快到慢。
		*/
		av_dict_set(¶m, "preset", "fast", 0);
		/* 
		tune的参数主要配合视频类型和视觉优化的参数。
		tune的值有： film：  电影、真人类型；
		animation：  动画；
		grain：      需要保留大量的grain时用；
		stillimage：  静态图像编码时使用；
		psnr：      为提高psnr做了优化的参数；
		ssim：      为提高ssim做了优化的参数；
		fastdecode： 可以快速解码的参数；
		zerolatency：零延迟，用在需要非常低的延迟的情况下，比如电视电话会议的编码。
		*/
		av_dict_set(¶m, "tune", "zerolatency", 0);
		/*
		画质,分别是baseline, extended, main, high
		1、Baseline Profile：基本画质。支持I/P 帧，只支持无交错（Progressive）和CAVLC；
		2、Extended profile：进阶画质。支持I/P/B/SP/SI 帧，只支持无交错（Progressive）和CAVLC；(用的少)
		3、Main profile：主流画质。提供I/P/B 帧，支持无交错（Progressive）和交错（Interlaced）， 也支持CAVLC 和CABAC 的支持；
		4、High profile：高级画质。在main Profile 的基础上增加了8x8内部预测、自定义量化、 无损视频编码和更多的YUV 格式；
　　	H.264 Baseline profile、Extended profile和Main profile都是针对8位样本数据、4:2:0格式(YUV)的视频序列。在相同配置情况下，
		High profile（HP）可以比Main profile（MP）降低10%的码率。 根据应用领域的不同，Baseline profile多应用于实时通信领域，
		Main profile多应用于流媒体领域，High profile则多应用于广电和存储领域。
		*/
		//av_dict_set(¶m, "profile", "main", 0);  
	}
	else if (pCodecCtx->codec_id == AV_CODEC_ID_H265 || pCodecCtx->codec_id == AV_CODEC_ID_HEVC)
	{
		av_dict_set(¶m, "preset", "fast", 0);
		av_dict_set(¶m, "tune", "zerolatency", 0);
	}
 
	//Output Info-----------------------------
	printf("--------------- out_file Information ----------------\n");
	//手工调试函数，输出tbn、tbc、tbr、PAR、DAR的含义
	av_dump_format(pFormatCtx, 0, out_file, 1);	//最后一个参数，如果是输出文件时，该值为1；如果是输入文件时，该值为0
	printf("-----------------------------------------------------\n");
 
	/* 查找编码器 */
	pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
	if (!pCodec) 
	{
		printf("Can not find encoder! \n");
		return -1;
	}
 
	/* 打开编码器 */
	if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0) 
	{
		printf("Failed to open encoder! \n");
		return -1;
	}
 
	pFrame = av_frame_alloc();
	picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
	picture_buf = (uint8_t *)av_malloc(picture_size);
	avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
 
	//Write File Header  
	/* 写文件头（对于某些没有文件头的封装格式，不需要此函数。比如说MPEG2TS） */
	avformat_write_header(pFormatCtx, NULL);
 
	/* Allocate the payload of a packet and initialize its fields with default values.  */
	av_new_packet(&pkt, picture_size);
 
	j = 1;
	y_size = pCodecCtx->width * pCodecCtx->height;
	unsigned int uiReadSize = 0;
	while (true)
	{
		//Read raw YUV data
		if (feof(in_file))	//文件结束
		{
			break;
		}
		else
		{
			uiReadSize = fread(picture_buf, 1, y_size * 3 / 2, in_file);
			if (uiReadSize <= 0)
			{
				printf("Failed to read raw data! \n");
				break;
			}
			else if (uiReadSize < y_size * 3 / 2)
			{
				//读取数据不满足一帧YUV数据，表示数据读取完
				break;
			}
		}
 
		pFrame->data[0] = picture_buf;					 // Y  
		pFrame->data[1] = picture_buf + y_size;			 // U   
		pFrame->data[2] = picture_buf + y_size * 5 / 4;  // V  
 
		//PTS  
		pFrame->pts = j*(video_st->time_base.den) / ((video_st->time_base.num) * 25);
 
		//Encode  
		/* 编码一帧视频。即将AVFrame（存储YUV像素数据）编码为AVPacket（存储H.264等格式的码流数据） */
		int ret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_picture);
		if (ret < 0)
		{
			printf("Failed to encode! \n");
			return -1;
		}
		if (got_picture == 1)
		{
			printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);  
			framecnt++;
			pkt.stream_index = video_st->index;
 
			/* 将编码后的视频码流写入文件 */
			ret = av_write_frame(pFormatCtx, &pkt);
 
			av_free_packet(&pkt);
		}
 
		j++;
	}
 
	//Flush Encoder  
	/* 输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket */
	ret = flush_encoder(pFormatCtx, 0);
	if (ret < 0) 
	{
		printf("Flushing encoder failed\n");
		return -1;
	}
 
	//Write file trailer  
	/* 写文件尾（对于某些没有文件头的封装格式，不需要此函数。比如说MPEG2TS） */
	av_write_trailer(pFormatCtx);
 
	//Clean  
	if (video_st) 
	{
		avcodec_close(video_st->codec);
		av_free(pFrame);
		av_free(picture_buf);
	}
	avio_close(pFormatCtx->pb);
	avformat_free_context(pFormatCtx);
	av_free_packet(&pkt);
 
	fclose(in_file);
 
	return 0;
}

#endif
